/****************************************************************************
 *
 * Copyright 2016 Samsung Electronics All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific
 * language governing permissions and limitations under the License.
 *
 ****************************************************************************/
/****************************************************************************
 * fs/procfs/fs_procfsproc.c
 *
 *   Copyright (C) 2013-2014 Gregory Nutt. All rights reserved.
 *   Author: Gregory Nutt <gnutt@nuttx.org>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 * 3. Neither the name NuttX nor the names of its contributors may be
 *    used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <tinyara/config.h>

#include <sys/socket.h>
#include <sys/types.h>
#include <sys/statfs.h>
#include <sys/stat.h>

#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <assert.h>
#include <errno.h>
#include <debug.h>

#ifdef CONFIG_DEBUG_MM_HEAPINFO
#include <tinyara/mm/mm.h>
#endif
#include <tinyara/arch.h>
#include <tinyara/sched.h>
#include <tinyara/kmalloc.h>
#include <tinyara/fs/fs.h>
#include <tinyara/fs/procfs.h>
#include <tinyara/fs/dirent.h>

#ifdef CONFIG_SCHED_CPULOAD
#include <tinyara/clock.h>
#endif

#include <arch/irq.h>

#if !defined(CONFIG_DISABLE_MOUNTPOINT) && defined(CONFIG_FS_PROCFS)
#ifndef CONFIG_FS_PROCFS_EXCLUDE_PROCESS

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/
/* Determines the size of an intermediate buffer that must be large enough
 * to handle the longest line generated by this logic.
 */

#define STATUS_LINELEN 128

/****************************************************************************
 * Private Types
 ****************************************************************************/
/* This enumeration identifies all of the task/thread nodes that can be
 * accessed via the procfs file system.
 */

/* The 'stat' and 'status' entries provide information about task/pthread.
 * Both are almost similar, a difference is a form of data.
 * The 'status' provides data with prefix like below, it is easier for humans to parse.
 *  StackSize: XXXX
 *  StackUsed: YYYY
 * On the other hand, the 'stat' provides only values which are shown by 'status' like "XXXX YYYY".
 * These values are used in kdbg commands, please refer to proc_stat_data_e in apps/system/utils/kdbg_utils.h
 */
enum proc_node_e {
	PROC_LEVEL0 = 0,			/* The top-level directory */
	PROC_STAT,					/* Task/thread status vales */
	PROC_STATUS,				/* Task/thread status */
	PROC_CMDLINE,				/* Task command line */
#ifdef CONFIG_SCHED_CPULOAD
	PROC_LOADAVG,				/* Average CPU utilization */
#endif
	PROC_STACK,					/* Task stack info */
	PROC_GROUP,					/* Group directory */
	PROC_GROUP_STATUS,			/* Task group status */
	PROC_GROUP_FD				/* Group file descriptors */
};

/* This structure associates a relative path name with an node in the task
 * procfs
 */

struct proc_node_s {
	FAR const char *relpath;	/* Relative path to the node */
	FAR const char *name;		/* Terminal node segment name */
	uint8_t node;				/* Type of node (see enum proc_node_e) */
	uint8_t dtype;				/* dirent type (see include/dirent.h) */
};

/* This structure describes one open "file" */

struct proc_file_s {
	struct procfs_file_s base;	/* Base open file structure */
	FAR const struct proc_node_s *node;	/* Describes the file node */
	pid_t pid;					/* Task/thread ID */
	char line[STATUS_LINELEN];	/* Pre-allocated buffer for formatted lines */
};

/* This structure describes one open "directory" */

struct proc_dir_s {
	struct procfs_dir_priv_s base;	/* Base directory private data */
	FAR const struct proc_node_s *node;	/* Directory node description */
	pid_t pid;					/* ID of task/thread for attributes */
};

/****************************************************************************
 * Private Function Prototypes
 ****************************************************************************/
/* Helpers */

static FAR const struct proc_node_s *proc_findnode(FAR const char *relpath);
static ssize_t proc_entry_stat(FAR struct proc_file_s *procfile, FAR struct tcb_s *tcb, FAR char *buffer, size_t buflen, off_t offset);
static ssize_t proc_entry_status(FAR struct proc_file_s *procfile, FAR struct tcb_s *tcb, FAR char *buffer, size_t buflen, off_t offset);
static ssize_t proc_entry_cmdline(FAR struct proc_file_s *procfile, FAR struct tcb_s *tcb, FAR char *buffer, size_t buflen, off_t offset);
#ifdef CONFIG_SCHED_CPULOAD
static ssize_t proc_entry_loadavg(FAR struct proc_file_s *procfile, FAR struct tcb_s *tcb, FAR char *buffer, size_t buflen, off_t offset);
#endif
static ssize_t proc_entry_stack(FAR struct proc_file_s *procfile, FAR struct tcb_s *tcb, FAR char *buffer, size_t buflen, off_t offset);
static ssize_t proc_entry_groupstatus(FAR struct proc_file_s *procfile, FAR struct tcb_s *tcb, FAR char *buffer, size_t buflen, off_t offset);
static ssize_t proc_entry_groupfd(FAR struct proc_file_s *procfile, FAR struct tcb_s *tcb, FAR char *buffer, size_t buflen, off_t offset);

/* File system methods */

static int proc_open(FAR struct file *filep, FAR const char *relpath, int oflags, mode_t mode);
static int proc_close(FAR struct file *filep);
static ssize_t proc_read(FAR struct file *filep, FAR char *buffer, size_t buflen);

static int proc_dup(FAR const struct file *oldp, FAR struct file *newp);

static int proc_opendir(const char *relpath, FAR struct fs_dirent_s *dir);
static int proc_closedir(FAR struct fs_dirent_s *dir);
static int proc_readdir(FAR struct fs_dirent_s *dir);
static int proc_rewinddir(FAR struct fs_dirent_s *dir);

static int proc_stat(FAR const char *relpath, FAR struct stat *buf);

/****************************************************************************
 * Private Variables
 ****************************************************************************/

/****************************************************************************
 * Public Variables
 ****************************************************************************/

/* See fs_mount.c -- this structure is explicitly externed there.
 * We use the old-fashioned kind of initializers so that this will compile
 * with any compiler.
 */

const struct procfs_operations proc_operations = {
	proc_open,					/* open */
	proc_close,					/* close */
	proc_read,					/* read */
	NULL,						/* write */

	proc_dup,					/* dup */

	proc_opendir,				/* opendir */
	proc_closedir,				/* closedir */
	proc_readdir,				/* readdir */
	proc_rewinddir,				/* rewinddir */

	proc_stat					/* stat */
};

/* These structures provide information about every node */

static const struct proc_node_s g_level0node = {
	"", "", (uint8_t)PROC_LEVEL0, DTYPE_DIRECTORY	/* Top-level directory */
};

static const struct proc_node_s g_stat = {
	"stat", "stat", (uint8_t)PROC_STAT, DTYPE_FILE	/* Task/thread status values */
};

static const struct proc_node_s g_status = {
	"status", "status", (uint8_t)PROC_STATUS, DTYPE_FILE	/* Task/thread status */
};

static const struct proc_node_s g_cmdline = {
	"cmdline", "cmdline", (uint8_t)PROC_CMDLINE, DTYPE_FILE	/* Task command line */
};

#ifdef CONFIG_SCHED_CPULOAD
static const struct proc_node_s g_loadavg = {
	"loadavg", "loadavg", (uint8_t)PROC_LOADAVG, DTYPE_FILE	/* Average CPU utilization */
};
#endif

static const struct proc_node_s g_stack = {
	"stack", "stack", (uint8_t)PROC_STACK, DTYPE_FILE	/* Task stack info */
};

static const struct proc_node_s g_group = {
	"group", "group", (uint8_t)PROC_GROUP, DTYPE_DIRECTORY	/* Group directory */
};

static const struct proc_node_s g_groupstatus = {
	"group/status", "status", (uint8_t)PROC_GROUP_STATUS, DTYPE_FILE	/* Task group status */
};

static const struct proc_node_s g_groupfd = {
	"group/fd", "fd", (uint8_t)PROC_GROUP_FD, DTYPE_FILE	/* Group file descriptors */
};

/* This is the list of all nodes */

static FAR const struct proc_node_s *const g_nodeinfo[] = {
	&g_stat,					/* Task/thread status values */
	&g_status,					/* Task/thread status */
	&g_cmdline,					/* Task command line */
#ifdef CONFIG_SCHED_CPULOAD
	&g_loadavg,					/* Average CPU utilization */
#endif
	&g_stack,					/* Task stack info */
	&g_group,					/* Group directory */
	&g_groupstatus,				/* Task group status */
	&g_groupfd					/* Group file descriptors */
};

#define PROC_NNODES (sizeof(g_nodeinfo)/sizeof(FAR const struct proc_node_s * const))

/* This is the list of all level0 nodes */

static const struct proc_node_s *const g_level0info[] = {
	&g_stat,					/* Task/thread status values */
	&g_status,					/* Task/thread status */
	&g_cmdline,					/* Task command line */
#ifdef CONFIG_SCHED_CPULOAD
	&g_loadavg,					/* Average CPU utilization */
#endif
	&g_stack,					/* Task stack info */
	&g_group,					/* Group directory */
};

#define PROC_NLEVEL0NODES (sizeof(g_level0info)/sizeof(FAR const struct proc_node_s * const))

/* This is the list of all group sub-directory nodes */

static const struct proc_node_s *const g_groupinfo[] = {
	&g_groupstatus,				/* Task group status */
	&g_groupfd					/* Group file descriptors */
};

#define PROC_NGROUPNODES (sizeof(g_groupinfo)/sizeof(FAR const struct proc_node_s * const))

/* Names of task/thread states */

static const char *g_statenames[] = {
	"Invalid",
	"Pending unlock",
	"Ready",
	"Running",
	"Inactive",
	"Semaphore wait",
	"To be unblocked wait"
#ifndef CONFIG_DISABLE_MQUEUE
	"Signal wait",
#endif
#ifndef CONFIG_DISABLE_MQUEUE
	"MQ not empty wait",
	"MQ no full wait"
#endif
};

static const char *g_ttypenames[4] = {
	"Task",
	"pthread",
	"Kernel thread",
	"--?--"
};

/****************************************************************************
 * Private Functions
 ****************************************************************************/

/****************************************************************************
 * Name: proc_findnode
 ****************************************************************************/

static FAR const struct proc_node_s *proc_findnode(FAR const char *relpath)
{
	int i;

	/* Search every string in g_nodeinfo or until a match is found */

	for (i = 0; i < PROC_NNODES; i++) {
		if (strcmp(g_nodeinfo[i]->relpath, relpath) == 0) {
			return g_nodeinfo[i];
		}
	}

	/* Not found */

	return NULL;
}

/****************************************************************************
 * Name: proc_stat
 ****************************************************************************/

static ssize_t proc_entry_stat(FAR struct proc_file_s *procfile, FAR struct tcb_s *tcb, FAR char *buffer, size_t buflen, off_t offset)
{
	size_t remaining;
	size_t linesize;
	size_t copysize;
	size_t totalsize;

	size_t curr_heap;
	size_t peak_heap;
	size_t peak_stack;
	pid_t ppid;
#ifdef CONFIG_DEBUG_MM_HEAPINFO
	struct mm_heap_s *heap;
	pid_t hash_pid;
#endif
#ifdef CONFIG_SCHED_CPULOAD
	int cpuload_idx;
	double load_value;
	struct cpuload_s cpuload;
	uint32_t total_cpuload = 0;
	uint32_t total_active = 0;
#endif

	remaining = buflen;
	totalsize = 0;

#ifdef CONFIG_STACK_COLORATION
	peak_stack = up_check_tcbstack(tcb);
#else
	peak_stack = -1;
#endif

	curr_heap = -1;
	peak_heap = -1;
#ifdef CONFIG_DEBUG_MM_HEAPINFO
	if (tcb->pid < CONFIG_SMP_NCPUS) {
		/* Idle task normally uses heap with index 0. */
		heap = kmm_get_heap_with_index(0);
	} else {
		heap = kmm_get_heap(tcb->stack_alloc_ptr);
	}
	if (heap == NULL) {
		return -1;
	}

	hash_pid = PIDHASH(tcb->pid);
	if (heap->alloc_list[hash_pid].pid == tcb->pid) {
		curr_heap = heap->alloc_list[hash_pid].curr_alloc_size;
		peak_heap = heap->alloc_list[hash_pid].peak_alloc_size;
	}
#endif

#if defined(CONFIG_SCHED_HAVE_PARENT) && !defined(HAVE_GROUP_MEMBERS)
	ppid = tcb->group->tg_ppid;
#else
	ppid = -1;
#endif
#ifdef CONFIG_SMP
	linesize = snprintf(procfile->line, STATUS_LINELEN, "%d %d %d %d %d %d %d %d %d %d", tcb->pid, ppid, tcb->sched_priority, tcb->flags, tcb->task_state, tcb->adj_stack_size, peak_stack, curr_heap, peak_heap, tcb->cpu);
#else
	linesize = snprintf(procfile->line, STATUS_LINELEN, "%d %d %d %d %d %d %d %d %d", tcb->pid, ppid, tcb->sched_priority, tcb->flags, tcb->task_state, tcb->adj_stack_size, peak_stack, curr_heap, peak_heap);
#endif
	copysize = procfs_memcpy(procfile->line, linesize, buffer, buflen, &offset);
	totalsize += copysize;

#ifdef CONFIG_SCHED_CPULOAD
	buffer += copysize;
	remaining -= copysize;
	if (totalsize >= buflen) {
		return totalsize;
	}
	linesize = snprintf(procfile->line, STATUS_LINELEN, " ");
	copysize = procfs_memcpy(procfile->line, linesize, buffer, remaining, &offset);
	totalsize += copysize;

	for (cpuload_idx = 0; cpuload_idx < SCHED_NCPULOAD; cpuload_idx++) {
		/* Get cpuload measurement data */
		(void)clock_cpuload(procfile->pid, cpuload_idx, &cpuload);

		for (int cpu = 0; cpu < CONFIG_SMP_NCPUS; cpu++) {
			
			if (cpuload.total[cpu] > 0) {
				load_value = (double)(100 * cpuload.active[cpu]) / (double)cpuload.total[cpu];
				total_cpuload += cpuload.total[cpu];
				total_active += cpuload.active[cpu];
			} else {
				load_value = 0.0f;
			}
			
#ifdef CONFIG_SMP
			buffer += copysize;
			remaining -= copysize;
			if (totalsize >= buflen) {
				return totalsize;
			}

			linesize = snprintf(procfile->line, STATUS_LINELEN, "%.1f-", load_value);
			copysize = procfs_memcpy(procfile->line, linesize, buffer, remaining, &offset);
			totalsize += copysize;
#endif

		}

		if (total_cpuload > 0) {
			load_value = (double)(100 * total_active) / (double)total_cpuload;
		} else {
			load_value = 0.0f;
		}

		buffer += copysize;
		remaining -= copysize;
		if (totalsize >= buflen) {
			return totalsize;
		}

		linesize = snprintf(procfile->line, STATUS_LINELEN, "%.1f ", load_value);
		copysize = procfs_memcpy(procfile->line, linesize, buffer, remaining, &offset);
		totalsize += copysize;
	}
#endif
#ifdef CONFIG_APP_BINARY_SEPARATION
	buffer += copysize;
	remaining -= copysize;

	if (totalsize >= buflen) {
		return totalsize;
	}

	char *bin_name = mm_get_app_heap_name(tcb->stack_alloc_ptr);
	if (bin_name == NULL) {
		bin_name = "kernel";
	}

	linesize = snprintf(procfile->line, STATUS_LINELEN, " %s", bin_name);
	copysize = procfs_memcpy(procfile->line, linesize, buffer, remaining, &offset);	
	totalsize += copysize;
#endif

#if CONFIG_TASK_NAME_SIZE > 0
	buffer += copysize;
	remaining -= copysize;

	if (totalsize >= buflen) {
		return totalsize;
	}

	linesize = snprintf(procfile->line, STATUS_LINELEN, " %s", tcb->name);
	copysize = procfs_memcpy(procfile->line, linesize, buffer, remaining, &offset);	
	totalsize += copysize;
#endif

	return totalsize;
}

/****************************************************************************
 * Name: proc_status
 ****************************************************************************/

static ssize_t proc_entry_status(FAR struct proc_file_s *procfile, FAR struct tcb_s *tcb, FAR char *buffer, size_t buflen, off_t offset)
{
	FAR const char *name;
	size_t remaining;
	size_t linesize;
	size_t copysize;
	size_t totalsize;

	remaining = buflen;
	totalsize = 0;

	/* Show the task name */

#if CONFIG_TASK_NAME_SIZE > 0
	name = tcb->name;
#else
	name = "<noname>";
#endif
	linesize = snprintf(procfile->line, STATUS_LINELEN, "%-12s%s\n", "Name:", name);
	copysize = procfs_memcpy(procfile->line, linesize, buffer, remaining, &offset);

	totalsize += copysize;
	buffer += copysize;
	remaining -= copysize;

	if (totalsize >= buflen) {
		return totalsize;
	}

	/* Show the thread type */

	linesize = snprintf(procfile->line, STATUS_LINELEN, "%-12s%s\n", "Type:", g_ttypenames[(tcb->flags & TCB_FLAG_TTYPE_MASK) >> TCB_FLAG_TTYPE_SHIFT]);
	copysize = procfs_memcpy(procfile->line, linesize, buffer, remaining, &offset);

	totalsize += copysize;
	buffer += copysize;
	remaining -= copysize;

	if (totalsize >= buflen) {
		return totalsize;
	}

	/* Show the thread state */

	linesize = snprintf(procfile->line, STATUS_LINELEN, "%-12s%s\n", "State:", g_statenames[tcb->task_state]);
	copysize = procfs_memcpy(procfile->line, linesize, buffer, remaining, &offset);

	totalsize += copysize;
	buffer += copysize;
	remaining -= copysize;

	if (totalsize >= buflen) {
		return totalsize;
	}

	/* Show the thread priority */

#ifdef CONFIG_PRIORITY_INHERITANCE
	linesize = snprintf(procfile->line, STATUS_LINELEN, "%-12s%d (%d)\n", "Priority:", tcb->sched_priority, tcb->base_priority);
#else
	linesize = snprintf(procfile->line, STATUS_LINELEN, "%-12s%d\n", "Priority:", tcb->sched_priority);
#endif
	copysize = procfs_memcpy(procfile->line, linesize, buffer, remaining, &offset);

	totalsize += copysize;
	buffer += copysize;
	remaining -= copysize;

	if (totalsize >= buflen) {
		return totalsize;
	}

	/* Show the scheduler */

	linesize = snprintf(procfile->line, STATUS_LINELEN, "%-12s%s", "Scheduler:", tcb->flags & TCB_FLAG_ROUND_ROBIN ? "SCHED_RR" : "SCHED_FIFO");
	copysize = procfs_memcpy(procfile->line, linesize, buffer, remaining, &offset);

	totalsize += copysize;
	buffer += copysize;
	remaining -= copysize;

	if (totalsize >= buflen) {
		return totalsize;
	}

	/* Show the signal mask */

#ifndef CONFIG_DISABLE_SIGNALS

	linesize = snprintf(procfile->line, STATUS_LINELEN, "\n%-12s%08x", "SigMask:", tcb->sigprocmask);
	copysize = procfs_memcpy(procfile->line, linesize, buffer, remaining, &offset);

	totalsize += copysize;
#endif
	return totalsize;
}

/****************************************************************************
 * Name: proc_cmdline
 ****************************************************************************/

static ssize_t proc_entry_cmdline(FAR struct proc_file_s *procfile, FAR struct tcb_s *tcb, FAR char *buffer, size_t buflen, off_t offset)
{
	FAR struct task_tcb_s *ttcb;
	FAR const char *name;
	FAR char **argv;
	size_t remaining;
	size_t linesize;
	size_t copysize;
	size_t totalsize;

	remaining = buflen;
	totalsize = 0;

	/* Show the task name */

#if CONFIG_TASK_NAME_SIZE > 0
	name = tcb->name;
#else
	name = "<noname>";
#endif
	linesize = strlen(name);
	memcpy(procfile->line, name, linesize);
	copysize = procfs_memcpy(procfile->line, linesize, buffer, remaining, &offset);

	totalsize += copysize;
	buffer += copysize;
	remaining -= copysize;

	if (totalsize >= buflen) {
		return totalsize;
	}
#ifndef CONFIG_DISABLE_PTHREAD
	/* Show the pthread argument */

	if ((tcb->flags & TCB_FLAG_TTYPE_MASK) == TCB_FLAG_TTYPE_PTHREAD) {
		FAR struct pthread_tcb_s *ptcb = (FAR struct pthread_tcb_s *)tcb;

		linesize = snprintf(procfile->line, STATUS_LINELEN, " 0x%p", ptcb->arg);
		copysize = procfs_memcpy(procfile->line, linesize, buffer, remaining, &offset);

		totalsize += copysize;
		buffer += copysize;
		remaining -= copysize;

		if (totalsize >= buflen) {
			return totalsize;
		}
	}
#endif

	/* Show the task argument list (skipping over the name) */

	ttcb = (FAR struct task_tcb_s *)tcb;

	for (argv = ttcb->argv + 1; *argv; argv++) {
		linesize = snprintf(procfile->line, STATUS_LINELEN, " %s", *argv);
		copysize = procfs_memcpy(procfile->line, linesize, buffer, remaining, &offset);

		totalsize += copysize;
		buffer += copysize;
		remaining -= copysize;

		if (totalsize >= buflen) {
			return totalsize;
		}
	}

	return totalsize;
}

/****************************************************************************
 * Name: proc_loadavg
 ****************************************************************************/
#ifdef CONFIG_SCHED_CPULOAD
static ssize_t proc_entry_loadavg(FAR struct proc_file_s *procfile, FAR struct tcb_s *tcb, FAR char *buffer, size_t buflen, off_t offset)
{
	struct cpuload_s cpuload;
	uint32_t intpart;
	uint32_t fracpart;
	size_t linesize;
	size_t copysize;
	size_t totalsize;
	int cpuload_idx;
	uint32_t total_cpuload = 0;
	uint32_t total_active = 0;

	/* Sample the counts for the thread.  clock_cpuload should only fail if
	 * the PID is not valid.  This could happen if the thread exited sometime
	 * after the procfs entry was opened.
	 */

	totalsize = 0;

	for (cpuload_idx = 0; cpuload_idx < SCHED_NCPULOAD; cpuload_idx++) {

		(void)clock_cpuload(procfile->pid, cpuload_idx, &cpuload);

		for (int cpu = 0; cpu < CONFIG_SMP_NCPUS; cpu++) {
			total_cpuload += cpuload.total[cpu];
			total_active += cpuload.active[cpu];
		}

		/* On the simulator, you may hit cpuload.total == 0, but probably never on
		 * real hardware.
		 */

		if (total_cpuload > 0) {
			uint32_t tmp;
			tmp = (1000 * total_active) / total_cpuload;
			intpart = tmp / 10;
			fracpart = tmp - 10 * intpart;
		} else {
			intpart = 0;
			fracpart = 0;
		}

		linesize = snprintf(procfile->line, STATUS_LINELEN, "%3d.%01d%%   ", intpart, fracpart);
		copysize = procfs_memcpy(procfile->line, linesize, buffer, buflen, &offset);
		totalsize += copysize;
		buffer += copysize;
	}

	return totalsize;
}
#endif

/****************************************************************************
 * Name: proc_stack
 ****************************************************************************/

static ssize_t proc_entry_stack(FAR struct proc_file_s *procfile, FAR struct tcb_s *tcb, FAR char *buffer, size_t buflen, off_t offset)
{
	size_t remaining;
	size_t linesize;
	size_t copysize;
	size_t totalsize;

	remaining = buflen;
	totalsize = 0;

	/* Show the stack base address */

	linesize = snprintf(procfile->line, STATUS_LINELEN, "%-12s0x%p\n", "StackBase:", tcb->adj_stack_ptr);
	copysize = procfs_memcpy(procfile->line, linesize, buffer, remaining, &offset);

	totalsize += copysize;
	buffer += copysize;
	remaining -= copysize;

	if (totalsize >= buflen) {
		return totalsize;
	}

	/* Show the stack size */

	linesize = snprintf(procfile->line, STATUS_LINELEN, "%-12s%ld", "StackSize:", (long)tcb->adj_stack_size);
	copysize = procfs_memcpy(procfile->line, linesize, buffer, remaining, &offset);

	totalsize += copysize;

#ifdef CONFIG_STACK_COLORATION
	buffer += copysize;
	remaining -= copysize;

	if (totalsize >= buflen) {
		return totalsize;
	}

	/* Show the stack size */

	linesize = snprintf(procfile->line, STATUS_LINELEN, "\n%-12s%ld", "StackUsed:", (long)up_check_tcbstack(tcb));
	copysize = procfs_memcpy(procfile->line, linesize, buffer, remaining, &offset);

	totalsize += copysize;
	buffer += copysize;
	remaining -= copysize;
#endif

	return totalsize;
}

/****************************************************************************
 * Name: proc_groupstatus
 ****************************************************************************/

static ssize_t proc_entry_groupstatus(FAR struct proc_file_s *procfile, FAR struct tcb_s *tcb, FAR char *buffer, size_t buflen, off_t offset)
{
	FAR struct task_group_s *group = tcb->group;
	size_t remaining;
	size_t linesize;
	size_t copysize;
	size_t totalsize;
#ifdef HAVE_GROUP_MEMBERS
	int i;
#endif

	DEBUGASSERT(group);

	remaining = buflen;
	totalsize = 0;

	/* Show the group IDs */

#ifdef HAVE_GROUP_MEMBERS
	linesize = snprintf(procfile->line, STATUS_LINELEN, "%-12s%d\n", "Group ID:", group->tg_gid);
	copysize = procfs_memcpy(procfile->line, linesize, buffer, remaining, &offset);

	totalsize += copysize;
	buffer += copysize;
	remaining -= copysize;

	if (totalsize >= buflen) {
		return totalsize;
	}

	linesize = snprintf(procfile->line, STATUS_LINELEN, "%-12s%d\n", "Parent ID:", group->tg_pgid);
	copysize = procfs_memcpy(procfile->line, linesize, buffer, remaining, &offset);

	totalsize += copysize;
	buffer += copysize;
	remaining -= copysize;

	if (totalsize >= buflen) {
		return totalsize;
	}
#endif

#if !defined(CONFIG_DISABLE_PTHREAD) && defined(CONFIG_SCHED_HAVE_PARENT)
	linesize = snprintf(procfile->line, STATUS_LINELEN, "%-12s%d\n", "Main task:", group->tg_task);
	copysize = procfs_memcpy(procfile->line, linesize, buffer, remaining, &offset);

	totalsize += copysize;
	buffer += copysize;
	remaining -= copysize;

	if (totalsize >= buflen) {
		return totalsize;
	}
#endif

	linesize = snprintf(procfile->line, STATUS_LINELEN, "%-12s0x%02x\n", "Flags:", group->tg_flags);
	copysize = procfs_memcpy(procfile->line, linesize, buffer, remaining, &offset);

	totalsize += copysize;
	buffer += copysize;
	remaining -= copysize;

	if (totalsize >= buflen) {
		return totalsize;
	}

	linesize = snprintf(procfile->line, STATUS_LINELEN, "%-12s%d", "Members:", group->tg_nmembers);
	copysize = procfs_memcpy(procfile->line, linesize, buffer, remaining, &offset);

	totalsize += copysize;
	buffer += copysize;
	remaining -= copysize;

#ifdef HAVE_GROUP_MEMBERS
	if (totalsize >= buflen) {
		return totalsize;
	}

	linesize = snprintf(procfile->line, STATUS_LINELEN, "\nMember IDs:");
	copysize = procfs_memcpy(procfile->line, linesize, buffer, remaining, &offset);

	totalsize += copysize;
	buffer += copysize;
	remaining -= copysize;

	if (totalsize >= buflen) {
		return totalsize;
	}

	for (i = 0; i < group->tg_nmembers; i++) {
		linesize = snprintf(procfile->line, STATUS_LINELEN, " %d");
		copysize = procfs_memcpy(procfile->line, linesize, buffer, remaining, &offset);

		totalsize += copysize;
		buffer += copysize;
		remaining -= copysize;

		if (totalsize >= buflen) {
			return totalsize;
		}
	}
#endif

	return totalsize;
}

/****************************************************************************
 * Name: proc_groupfd
 ****************************************************************************/

static ssize_t proc_entry_groupfd(FAR struct proc_file_s *procfile, FAR struct tcb_s *tcb, FAR char *buffer, size_t buflen, off_t offset)
{
	FAR struct task_group_s *group = tcb->group;
#if CONFIG_NFILE_DESCRIPTORS > 0	/* Guaranteed to be true */
	FAR struct file *file;
#endif
#if CONFIG_NSOCKET_DESCRIPTORS > 0
	// To Do: dup is not working until BIND_TASK is implmented
	// FAR struct socket *socket;
#endif
	size_t remaining;
	size_t linesize;
	size_t copysize;
	size_t totalsize;
	int i;

	DEBUGASSERT(group);

	remaining = buflen;
	totalsize = 0;

#if CONFIG_NFILE_DESCRIPTORS > 0	/* Guaranteed to be true */
	linesize = snprintf(procfile->line, STATUS_LINELEN, "%3-s %-8s %s", "FD", "POS", "OFLAGS");
	copysize = procfs_memcpy(procfile->line, linesize, buffer, remaining, &offset);

	totalsize += copysize;
	buffer += copysize;
	remaining -= copysize;

	if (totalsize >= buflen) {
		return totalsize;
	}

	/* Examine each open file descriptor */

	for (i = 0, file = group->tg_filelist.fl_files; i < CONFIG_NFILE_DESCRIPTORS; i++, file++) {
		/* Is there an inode associated with the file descriptor? */

		if (file->f_inode) {
			linesize = snprintf(procfile->line, STATUS_LINELEN, "\n%3d %8ld %04x", i, (long)file->f_pos, file->f_oflags);
			copysize = procfs_memcpy(procfile->line, linesize, buffer, remaining, &offset);

			totalsize += copysize;
			buffer += copysize;
			remaining -= copysize;

			if (totalsize >= buflen) {
				return totalsize;
			}
		}
	}
#endif

#if 0 // To Do: dup is not working until BIND_TASK is implmented
#if CONFIG_NSOCKET_DESCRIPTORS > 0
	linesize = snprintf(procfile->line, STATUS_LINELEN, "\n%3-s %-2s %-3s %s", "SD", "RF", "TYP", "FLAGS");
	copysize = procfs_memcpy(procfile->line, linesize, buffer, remaining, &offset);

	totalsize += copysize;
	buffer += copysize;
	remaining -= copysize;

	if (totalsize >= buflen) {
		return totalsize;
	}

	/* Examine each open socket descriptor */

	for (i = 0, socket = group->tg_socketlist.sl_sockets; i < CONFIG_NSOCKET_DESCRIPTORS; i++, socket++) {
		/* Is there an connection associated with the socket descriptor? */

		if (socket->conn) {
			linesize = snprintf(procfile->line, STATUS_LINELEN, "\n%3d %2d %3d %02x", i + CONFIG_NFILE_DESCRIPTORS, (long)socket->conn->state, socket->conn->type, socket->conn->flags);
			copysize = procfs_memcpy(procfile->line, linesize, buffer, remaining, &offset);

			totalsize += copysize;
			buffer += copysize;
			remaining -= copysize;

			if (totalsize >= buflen) {
				return totalsize;
			}
		}
	}
#endif
#endif // if 0
	return totalsize;
}

/****************************************************************************
 * Name: proc_open
 ****************************************************************************/

static int proc_open(FAR struct file *filep, FAR const char *relpath, int oflags, mode_t mode)
{
	FAR struct proc_file_s *procfile;
	FAR const struct proc_node_s *node;
	FAR struct tcb_s *tcb;
	FAR char *ptr;
	irqstate_t flags;
	unsigned long tmp;
	pid_t pid;

	fvdbg("Open '%s'\n", relpath);

	/* PROCFS is read-only.  Any attempt to open with any kind of write
	 * access is not permitted.
	 *
	 * REVISIT:  Write-able proc files could be quite useful.
	 */

	if ((oflags & O_WRONLY) != 0 || (oflags & O_RDONLY) == 0) {
		fdbg("ERROR: Only O_RDONLY supported\n");
		return -EACCES;
	}

	/* The first segment of the relative path should be a task/thread ID */

	ptr = NULL;
	tmp = strtoul(relpath, &ptr, 10);

	if (!ptr || *ptr != '/') {
		fdbg("ERROR: Invalid path \"%s\"\n", relpath);
		return -ENOENT;
	}

	/* Skip over the slash */

	ptr++;

	/* A valid PID would be in the range of 0-32767 (0 is reserved for the
	 * IDLE thread).
	 */

	if (tmp >= 32768) {
		fdbg("ERROR: Invalid PID %ld\n", tmp);
		return -ENOENT;
	}

	/* Now verify that a task with this task/thread ID exists */

	pid = (pid_t)tmp;

	flags = enter_critical_section();
	tcb = sched_gettcb(pid);
	leave_critical_section(flags);

	if (!tcb) {
		fdbg("ERROR: PID %d is no longer valid\n", (int)pid);
		return -ENOENT;
	}

	/* The remaining segments of the relpath should be a well known node in
	 * the task/thread tree.
	 */

	node = proc_findnode(ptr);
	if (!node) {
		fdbg("ERROR: Invalid path \"%s\"\n", relpath);
		return -ENOENT;
	}

	/* The node must be a file, not a directory */

	if (node->dtype != DTYPE_FILE) {
		fdbg("ERROR: Path \"%s\" is a directory\n", relpath);
		return -EISDIR;
	}

	/* Allocate a container to hold the task and node selection */

	procfile = (FAR struct proc_file_s *)kmm_zalloc(sizeof(struct proc_file_s));
	if (!procfile) {
		fdbg("ERROR: Failed to allocate file container\n");
		return -ENOMEM;
	}

	/* Initialize the file container */

	procfile->pid = pid;
	procfile->node = node;

	/* Save the index as the open-specific state in filep->f_priv */

	filep->f_priv = (FAR void *)procfile;
	return OK;
}

/****************************************************************************
 * Name: proc_close
 ****************************************************************************/

static int proc_close(FAR struct file *filep)
{
	FAR struct proc_file_s *procfile;

	/* Recover our private data from the struct file instance */

	procfile = (FAR struct proc_file_s *)filep->f_priv;
	DEBUGASSERT(procfile);

	/* Release the file container structure */

	kmm_free(procfile);
	filep->f_priv = NULL;
	return OK;
}

/****************************************************************************
 * Name: proc_read
 ****************************************************************************/

static ssize_t proc_read(FAR struct file *filep, FAR char *buffer, size_t buflen)
{
	FAR struct proc_file_s *procfile;
	FAR struct tcb_s *tcb;
	irqstate_t flags;
	ssize_t ret;

	fvdbg("buffer=%p buflen=%d\n", buffer, (int)buflen);

	/* Recover our private data from the struct file instance */

	procfile = (FAR struct proc_file_s *)filep->f_priv;
	DEBUGASSERT(procfile);

	/* Verify that the thread is still valid */

	flags = enter_critical_section();
	tcb = sched_gettcb(procfile->pid);

	if (!tcb) {
		fdbg("ERROR: PID %d is not valid\n", (int)procfile->pid);
		leave_critical_section(flags);
		return -ENODEV;
	}

	/* Provide the requested data */

	switch (procfile->node->node) {
	case PROC_STAT:				/* Task/thread status */
		ret = proc_entry_stat(procfile, tcb, buffer, buflen, filep->f_pos);
		break;

	case PROC_STATUS:			/* Task/thread status */
		ret = proc_entry_status(procfile, tcb, buffer, buflen, filep->f_pos);
		break;

	case PROC_CMDLINE:			/* Task command line */
		ret = proc_entry_cmdline(procfile, tcb, buffer, buflen, filep->f_pos);
		break;

#ifdef CONFIG_SCHED_CPULOAD
	case PROC_LOADAVG:			/* Average CPU utilization */
		ret = proc_entry_loadavg(procfile, tcb, buffer, buflen, filep->f_pos);
		break;
#endif
	case PROC_STACK:			/* Task stack info */
		ret = proc_entry_stack(procfile, tcb, buffer, buflen, filep->f_pos);
		break;

	case PROC_GROUP_STATUS:	/* Task group status */
		ret = proc_entry_groupstatus(procfile, tcb, buffer, buflen, filep->f_pos);
		break;

	case PROC_GROUP_FD:		/* Group file descriptors */
		ret = proc_entry_groupfd(procfile, tcb, buffer, buflen, filep->f_pos);
		break;

	default:
		ret = -EINVAL;
		break;
	}

	leave_critical_section(flags);

	/* Update the file offset */

	if (ret > 0) {
		filep->f_pos += ret;
	}

	return ret;
}

/****************************************************************************
 * Name: proc_dup
 *
 * Description:
 *   Duplicate open file data in the new file structure.
 *
 ****************************************************************************/

static int proc_dup(FAR const struct file *oldp, FAR struct file *newp)
{
	FAR struct proc_file_s *oldfile;
	FAR struct proc_file_s *newfile;

	fvdbg("Dup %p->%p\n", oldp, newp);

	/* Recover our private data from the old struct file instance */

	oldfile = (FAR struct proc_file_s *)oldp->f_priv;
	DEBUGASSERT(oldfile);

	/* Allocate a new container to hold the task and node selection */

	newfile = (FAR struct proc_file_s *)kmm_malloc(sizeof(struct proc_file_s));
	if (!newfile) {
		fdbg("ERROR: Failed to allocate file container\n");
		return -ENOMEM;
	}

	/* The copy the file information from the old container to the new */

	memcpy(newfile, oldfile, sizeof(struct proc_file_s));

	/* Save the new container in the new file structure */

	newp->f_priv = (FAR void *)newfile;
	return OK;
}

/****************************************************************************
 * Name: proc_opendir
 *
 * Description:
 *   Open a directory for read access
 *
 ****************************************************************************/

static int proc_opendir(FAR const char *relpath, FAR struct fs_dirent_s *dir)
{
	FAR struct proc_dir_s *procdir;
	FAR const struct proc_node_s *node;
	FAR struct tcb_s *tcb;
	irqstate_t flags;
	unsigned long tmp;
	FAR char *ptr;
	pid_t pid;

	fvdbg("relpath: \"%s\"\n", relpath ? relpath : "NULL");
	DEBUGASSERT(relpath && dir && !dir->u.procfs);

	/* The relative must be either:
	 *
	 *  (1) "<pid>" - The sub-directory of task/thread attributes, or
	 *  (2) The name of a directory node under <pid>
	 */

	/* Otherwise, the relative path should be a valid task/thread ID */

	ptr = NULL;
	tmp = strtoul(relpath, &ptr, 10);

	if (!ptr || (*ptr != '\0' && *ptr != '/')) {
		/* strtoul failed or there is something in the path after the pid */

		fdbg("ERROR: Invalid path \"%s\"\n", relpath);
		return -ENOENT;
	}

	/* A valid PID would be in the range of 0-32767 (0 is reserved for the
	 * IDLE thread).
	 */

	if (tmp >= 32768) {
		fdbg("ERROR: Invalid PID %ld\n", tmp);
		return -ENOENT;
	}

	/* Now verify that a task with this task/thread ID exists */

	pid = (pid_t)tmp;

	flags = enter_critical_section();
	tcb = sched_gettcb(pid);
	leave_critical_section(flags);

	if (!tcb) {
		fdbg("ERROR: PID %d is not valid\n", (int)pid);
		return -ENOENT;
	}

	/* Allocate the directory structure.  Note that the index and procentry
	 * pointer are implicitly nullified by kmm_zalloc().  Only the remaining,
	 * non-zero entries will need be initialized.
	 */

	procdir = (FAR struct proc_dir_s *)kmm_zalloc(sizeof(struct proc_dir_s));
	if (!procdir) {
		fdbg("ERROR: Failed to allocate the directory structure\n");
		return -ENOMEM;
	}

	/* Was the <pid> the final element of the path? */

	if (*ptr != '\0' && strcmp(ptr, "/") != 0) {
		/* There is something in the path after the pid.  Skip over the path
		 * segment delimiter and see if we can identify the node of interest.
		 */

		ptr++;
		node = proc_findnode(ptr);
		if (!node) {
			fdbg("ERROR: Invalid path \"%s\"\n", relpath);
			kmm_free(procdir);
			return -ENOENT;
		}

		/* The node must be a directory, not a file */

		if (node->dtype != DTYPE_DIRECTORY) {
			fdbg("ERROR: Path \"%s\" is not a directory\n", relpath);
			kmm_free(procdir);
			return -ENOTDIR;
		}

		/* This is a second level directory */

		procdir->base.level = 2;
		procdir->base.nentries = PROC_NGROUPNODES;
		procdir->node = node;
	} else {
		/* Use the special level0 node */

		procdir->base.level = 1;
		procdir->base.nentries = PROC_NLEVEL0NODES;
		procdir->node = &g_level0node;
	}

	procdir->pid = pid;
	dir->u.procfs = (FAR void *)procdir;
	return OK;
}

/****************************************************************************
 * Name: proc_closedir
 *
 * Description: Close the directory listing
 *
 ****************************************************************************/

static int proc_closedir(FAR struct fs_dirent_s *dir)
{
	FAR struct proc_dir_s *priv;

	DEBUGASSERT(dir && dir->u.procfs);
	priv = dir->u.procfs;

	if (priv) {
		kmm_free(priv);
	}

	dir->u.procfs = NULL;
	return OK;
}

/****************************************************************************
 * Name: proc_readdir
 *
 * Description: Read the next directory entry
 *
 ****************************************************************************/

static int proc_readdir(struct fs_dirent_s *dir)
{
	FAR struct proc_dir_s *procdir;
	FAR const struct proc_node_s *node = NULL;
	FAR struct tcb_s *tcb;
	unsigned int index;
	irqstate_t flags;
	pid_t pid;
	int ret;

	DEBUGASSERT(dir && dir->u.procfs);
	procdir = dir->u.procfs;

	/* Have we reached the end of the directory */

	index = procdir->base.index;
	if (index >= procdir->base.nentries) {
		/* We signal the end of the directory by returning the special
		 * error -ENOENT
		 */

		fvdbg("Entry %d: End of directory\n", index);
		ret = -ENOENT;
	}

	/* No, we are not at the end of the directory */

	else {
		/* Verify that the pid still refers to an active task/thread */

		pid = procdir->pid;

		flags = enter_critical_section();
		tcb = sched_gettcb(pid);
		leave_critical_section(flags);

		if (!tcb) {
			fdbg("ERROR: PID %d is no longer valid\n", (int)pid);
			return -ENOENT;
		}

		/* The TCB is still valid (or at least was when we entered this function) */
		/* Handle the directory listing by the node type */

		switch (procdir->node->node) {
		case PROC_LEVEL0:		/* Top level directory */
			DEBUGASSERT(procdir->base.level == 1);
			node = g_level0info[index];
			break;

		case PROC_GROUP:		/* Group sub-directory */
			DEBUGASSERT(procdir->base.level == 2);
			node = g_groupinfo[index];
			break;

		default:
			return -ENOENT;
		}

		/* Save the filename and file type */

		dir->fd_dir.d_type = node->dtype;
		strncpy(dir->fd_dir.d_name, node->name, NAME_MAX + 1);

		/* Set up the next directory entry offset.  NOTE that we could use the
		 * standard f_pos instead of our own private index.
		 */

		procdir->base.index = index + 1;
		ret = OK;
	}

	return ret;
}

/****************************************************************************
 * Name: proc_rewindir
 *
 * Description: Reset directory read to the first entry
 *
 ****************************************************************************/

static int proc_rewinddir(struct fs_dirent_s *dir)
{
	FAR struct proc_dir_s *priv;

	DEBUGASSERT(dir && dir->u.procfs);
	priv = dir->u.procfs;

	priv->base.index = 0;
	return OK;
}

/****************************************************************************
 * Name: proc_stat
 *
 * Description: Return information about a file or directory
 *
 ****************************************************************************/

static int proc_stat(const char *relpath, struct stat *buf)
{
	FAR const struct proc_node_s *node;
	FAR struct tcb_s *tcb;
	unsigned long tmp;
	FAR char *ptr;
	irqstate_t flags;
	pid_t pid;

	/* Two path forms are accepted:
	 *
	 * "<pid>" - If <pid> refers to a currently active task/thread, then it
	 *   is a directory
	 * "<pid>/<node>" - If <node> is a recognized node then, then it
	 *   is a file or directory.
	 */

	ptr = NULL;
	tmp = strtoul(relpath, &ptr, 10);

	if (!ptr) {
		fdbg("ERROR: Invalid path \"%s\"\n", relpath);
		return -ENOENT;
	}

	/* A valid PID would be in the range of 0-32767 (0 is reserved for the
	 * IDLE thread).
	 */

	if (tmp >= 32768) {
		fdbg("ERROR: Invalid PID %ld\n", tmp);
		return -ENOENT;
	}

	/* Now verify that a task with this task/thread ID exists */

	pid = (pid_t)tmp;

	flags = enter_critical_section();
	tcb = sched_gettcb(pid);
	leave_critical_section(flags);

	if (!tcb) {
		fdbg("ERROR: PID %d is no longer valid\n", (int)pid);
		return -ENOENT;
	}

	/* Was the <pid> the final element of the path? */

	if (*ptr == '\0' || strcmp(ptr, "/") == 0) {
		/* Yes ... It's a read-only directory */

		buf->st_mode = S_IFDIR | S_IROTH | S_IRGRP | S_IRUSR;
	}

	/* Verify that the process ID is followed by valid path segment delimiter */

	else if (*ptr != '/') {
		/* We are required to return -ENOENT all all invalid paths */

		fdbg("ERROR: Bad delimiter '%c' in relpath '%s'\n", *ptr, relpath);
		return -ENOENT;
	} else {
		/* Otherwise, the second segment of the relpath should be a well
		 * known node of the task/thread directory structure.
		 */

		/* Skip over the path segment delimiter */

		ptr++;

		/* Lookup the well-known node associated with the relative path. */

		node = proc_findnode(ptr);
		if (!node) {
			fdbg("ERROR: Invalid path \"%s\"\n", relpath);
			return -ENOENT;
		}

		/* If the node exists, it is the name for a read-only file or
		 * directory.
		 */

		if (node->dtype == DTYPE_FILE) {
			buf->st_mode = S_IFREG | S_IROTH | S_IRGRP | S_IRUSR;
		} else {
			buf->st_mode = S_IFDIR | S_IROTH | S_IRGRP | S_IRUSR;
		}
	}

	/* File/directory size, access block size */

	buf->st_size = 0;
	buf->st_blksize = 0;
	buf->st_blocks = 0;
	return OK;
}

/****************************************************************************
 * Public Functions
 ****************************************************************************/

#endif							/* CONFIG_FS_PROC_EXCLUDE_PROCESS */
#endif							/* !CONFIG_DISABLE_MOUNTPOINT && CONFIG_FS_PROCFS */
